﻿using System;
using System.Linq;
using System.Reflection;
using Volo.Abp.Localization;
using Volo.Abp.Reflection;
using Xms.Abp.Data;
using Xms.Abp.Validation.StringValues;

namespace Xms.Abp.Features;

public class XmsFeaturesDefinitionProvider : FeaturesDefinitionProvider
{
    private readonly ITypeFinder _typeFinder;

    public XmsFeaturesDefinitionProvider(ITypeFinder typeFinder)
    {
        _typeFinder = typeFinder;
    }

    public override void Define(IFeaturesDefinitionContext context)
    {
        var featureTypes = _typeFinder.Types.Where(type =>
            type.IsPublic &&
            !type.IsAbstract &&
            type.IsClass &&
            typeof(IFeatures).IsAssignableFrom(type) &&
            type.GetCustomAttribute<FeaturesDefinitionAttribute>() != null
        );
        foreach (var featureType in featureTypes)
        {
            var resourceType = FeaturesHelper.GetLocalizationResource(featureType);
            var define = context.AddGroup(FeaturesHelper.GetTypeName(featureType), DisplayName(resourceType, featureType));
            RecursivelyDefinition(define, resourceType, featureType.GetNestedTypes(), FeaturesHelper.GetTypeName(featureType));
        }
    }

    public void RecursivelyDefinition(FeaturesGroupDefinition group, Type resourceType, Type[] types, string nameSpace)
    {
        foreach (var type in types)
        {
            var define = group.AddFeature(
                FeaturesHelper.GetSubFeatureName(type, nameSpace),
                defaultValue: "false",
                displayName: DisplayName(resourceType, type, nameSpace),
                description: Description(resourceType, type, nameSpace),
                valueType: DataType.Boolean);
            RecursivelyDefinition(define, resourceType, type.GetNestedTypes(), nameSpace + "." + FeaturesHelper.GetTypeName(type));
            SetPropertiesDefinition(define, resourceType, type, nameSpace);
        }
    }

    public void RecursivelyDefinition(FeatureValueDefinition define, Type resourceType, Type[] types, string nameSpace)
    {
        foreach (var type in types)
        {
            var def = define.CreateChild(
                FeaturesHelper.GetSubFeatureName(type, nameSpace),
                defaultValue: "false",
                displayName: DisplayName(resourceType, type, nameSpace),
                description: Description(resourceType, type, nameSpace),
                valueType: DataType.Boolean);
            RecursivelyDefinition(def, resourceType, type.GetNestedTypes(), nameSpace + "." + FeaturesHelper.GetTypeName(type));
            SetPropertiesDefinition(define, resourceType, type, nameSpace);
        }
    }

    private void SetPropertiesDefinition(FeatureValueDefinition define, Type resourceType, Type type, string nameSpace)
    {
        var properties = type.GetProperties().Where(r => r.GetCustomAttribute<FeatureIgnoreAttribute>() == null);
        foreach (var property in properties)
        {
            var attr = property.GetCustomAttribute<FeatureValueAttribute>();
            var defaultValue = attr?.DefaultValue ?? (property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType).ToString() : default);
            var stringValueType = attr?.ValueType ?? property.PropertyType.ToStringValueType();
            var propertyName = property.Name.RemovePostFix("Value");
            define.CreateChild(
                FeaturesHelper.GetSubFeatureName(type, nameSpace, attr?.CustomName ?? propertyName),
                defaultValue: defaultValue,
                displayName: DisplayName(resourceType, type, nameSpace, propertyName),
                description: Description(resourceType, type, nameSpace, propertyName),
                valueType: stringValueType,
                isVisibleToClients: attr?.IsVisibleToClients ?? true,
                isAvailableToHost: attr?.IsAvailableToHost ?? true
                );
        }
    }

    private static LocalizableString L(Type resourceType, string key)
    {
        return new LocalizableString(resourceType, key);
    }

    private static LocalizableString DisplayName(Type resourceType, Type type, string nameSpace = null, string propertyName = null)
    {
        return L(resourceType, FeaturesHelper.GetDisplayNameKey(type, nameSpace, propertyName));
    }

    private static LocalizableString Description(Type resourceType, Type type, string nameSpace = null, string propertyName = null)
    {
        return L(resourceType, FeaturesHelper.GetDescriptionKey(type, nameSpace, propertyName));
    }
}
